package cn.ybx.seaIslands;

import cn.ybx.seaIslands.exceptions.MapFormatErrorException;
import cn.ybx.seaIslands.models.Island;
import cn.ybx.seaIslands.models.Point;
import cn.ybx.seaIslands.models.RectangleSeaIslandsMap;
import cn.ybx.seaIslands.models.SeaIslandsMap;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

public class MapEngine {
    private static MapEngine mapEngineInstance = null;

    public static final String MAP_TYPE_RECTANGLE = "rectangle";
    private SeaIslandsMap seaIslandsMap = null;
    private char[][] gridMap;

    private MapEngine(File mapFile, String mapType) throws IOException, MapFormatErrorException {
        parseMap(mapFile, mapType);
    }

    public static MapEngine getInstance(File mapFile, String mapType) throws IOException, MapFormatErrorException {
        if (mapEngineInstance == null) {
            return new MapEngine(mapFile, mapType);
        }
        return mapEngineInstance;
    }

    public SeaIslandsMap getSeaIslandsMap() {
        return seaIslandsMap;
    }

    private void parseMap(File mapFile, String mapType) throws IOException, MapFormatErrorException {
        if (mapType.equals(MAP_TYPE_RECTANGLE)) {
            gridMap = readRectangleMapFile(mapFile);
            boolean[][] visitedGrid = new boolean[gridMap[0].length][gridMap.length];
            seaIslandsMap = new RectangleSeaIslandsMap(gridMap);
            for (int i = 0; i < gridMap.length; i++) {
                for (int j = 0; j < gridMap[0].length; j++) {
                    if (!visitedGrid[i][j] && gridMap[i][j] == 'x') {
                        Island newIsland = new Island();
                        doBFS(newIsland, gridMap, visitedGrid, i, j);
                        seaIslandsMap.getIslands().add(newIsland);
                    }
                }
            }
        }
    }

    private void doBFS(Island island, char[][] gridMap, boolean[][] visitedGrid, int latitude, int logitude) {
        Point thePoint = new Point(latitude, logitude);
        if (seaIslandsMap.isInsideTheMap(thePoint) && gridMap[latitude][logitude] == 'x' && !visitedGrid[latitude][logitude]) {
            island.getLandCoordinates().add(thePoint);
            visitedGrid[latitude][logitude] = true;

            doBFS(island, gridMap, visitedGrid, latitude, logitude - 1);

            doBFS(island, gridMap, visitedGrid, latitude + 1, logitude);

            doBFS(island, gridMap, visitedGrid, latitude, logitude + 1);

            doBFS(island, gridMap, visitedGrid, latitude - 1, logitude);

        }
    }


    public char[][] readRectangleMapFile(File mapFile) throws IOException, MapFormatErrorException {
        List<String> fileLines = IOUtils.readLines(new FileReader(mapFile));
        char[][] gridMap;
        int width;
        int heigth;
        if (fileLines != null && fileLines.size() > 0 && fileLines.get(0).length() > 0) {
            heigth = fileLines.size();
            width = fileLines.get(0).length();
            gridMap = new char[width][heigth];
            int i = 0;
            for (String lineStr : fileLines) {
                if (lineStr.length() == width) {
                    gridMap[i] = lineStr.toCharArray();
                    i++;
                } else {
                    throw new MapFormatErrorException();
                }
            }
        } else {
            throw new MapFormatErrorException();
        }
        return gridMap;
    }

    public int getIslandSizeByCoordinates(Point thePoint) {
        if (!this.seaIslandsMap.isInsideTheMap(thePoint)) {
            return -1;
        }
        Island islandByCoordinates = this.seaIslandsMap.findIslandByCoordinates(thePoint);
        if (islandByCoordinates != null) {
            return islandByCoordinates.getLandCoordinates().size();
        } else {
            return -1;
        }
    }

    public int isInTheSameIsland(Point pointA, Point pointB) {
        if (!(this.seaIslandsMap.isInsideTheMap(pointA) && this.seaIslandsMap.isInsideTheMap(pointB))) {
            return -1;
        }
        Island islandA = this.seaIslandsMap.findIslandByCoordinates(pointA);
        Island islandB = this.seaIslandsMap.findIslandByCoordinates(pointB);
        if (islandA == null || islandB == null) {
            return -1;
        } else {
            if (islandA == islandB) {
                return 1;
            } else {
                return 0;
            }

        }
    }

    public char[][] getGridMap() {
        return gridMap;
    }

    public void printGridMap() {
        if (gridMap == null) System.out.println("no map!");
        for (char[] aGridMap : gridMap) {
            for (char aChar : aGridMap) {
                System.out.print(aChar);
            }
            System.out.println();
        }
    }

    public Island getTheMaxSizeIsland() {
        return Collections.max(this.seaIslandsMap.getIslands());
    }
}